package de.ctnetzapplet.controller;

import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.Calendar;

import javax.swing.AbstractButton;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JList;
import javax.swing.JTextField;
import javax.swing.Timer;


import de.XPortLib.TimerEvent;
import de.XPortLib.XPortControllerAsync;
import de.XPortLib.Exceptions.DataTransmissionException;
import de.XPortLib.Exceptions.TCPCommunicationException;
import de.XPortLib.Interfaces.XPortCallbackIF;
import de.XPortLib.Interfaces.XPortControllerAsyncIF;
import de.afriese.swing.SwingTools;
import de.ctnetzapplet.view.MainApplet;

public class MainAppletController implements XPortCallbackIF, ActionListener, ItemListener {

    private XPortControllerAsyncIF XPort;
    private MainApplet applet;
    private JButton[] relaisButtons;
    private Timer autoUpdateTimer;
    private javax.swing.JButton btnDeleteTimer;
    private javax.swing.JButton btnReadRelais;
    private javax.swing.JButton btnReadTimer;
    private javax.swing.JButton btnRelais1;
    private javax.swing.JButton btnRelais2;
    private javax.swing.JButton btnRelais3;
    private javax.swing.JButton btnRelais4;
    private javax.swing.JButton btnRelais5;
    private javax.swing.JButton btnRelais6;
    private javax.swing.JButton btnRelaisAux;
    private javax.swing.JButton btnSetDateTime;
    private javax.swing.JButton btnUpdateSettings;
    private javax.swing.JButton btnWriteTimer;
    private javax.swing.JComboBox timer_day;
    private javax.swing.JCheckBox timer_getDateTime;
    private javax.swing.JComboBox timer_hour;
    private javax.swing.JComboBox timer_minute;
    private javax.swing.JComboBox timer_month;
    private javax.swing.JComboBox timer_nr;
    private javax.swing.JList timer_poweronoff;
    private javax.swing.JList timer_relais;
    private javax.swing.JComboBox timer_second;
    private javax.swing.JList timer_weekday;
    private javax.swing.JComboBox timer_year;
    private javax.swing.JTextField txtIP;
    private javax.swing.JTextField txtPort;
    private javax.swing.JCheckBox autoUpdate;

    public MainAppletController(MainApplet applet) {
        this.applet = applet;

        btnDeleteTimer = findComponent(JButton.class, "btnDeleteTimer");
        btnReadRelais = findComponent(JButton.class, "btnReadRelais");
        btnReadTimer = findComponent(JButton.class, "btnReadTimer");
        btnRelais1 = findComponent(JButton.class, "btnRelais1");
        btnRelais2 = findComponent(JButton.class, "btnRelais2");
        btnRelais3 = findComponent(JButton.class, "btnRelais3");
        btnRelais4 = findComponent(JButton.class, "btnRelais4");
        btnRelais5 = findComponent(JButton.class, "btnRelais5");
        btnRelais6 = findComponent(JButton.class, "btnRelais6");
        btnRelaisAux = findComponent(JButton.class, "btnRelaisAux");
        btnSetDateTime = findComponent(JButton.class, "btnSetDateTime");
        btnUpdateSettings = findComponent(JButton.class, "btnUpdateSettings");
        btnWriteTimer = findComponent(JButton.class, "btnWriteTimer");
        timer_day = findComponent(JComboBox.class, "timer_day");
        timer_getDateTime = findComponent(JCheckBox.class, "timer_getDateTime");
        timer_hour = findComponent(JComboBox.class, "timer_hour");
        timer_minute = findComponent(JComboBox.class, "timer_minute");
        timer_month = findComponent(JComboBox.class, "timer_month");
        timer_nr = findComponent(JComboBox.class, "timer_nr");
        timer_poweronoff = findComponent(JList.class, "timer_poweronoff");
        timer_relais = findComponent(JList.class, "timer_relais");
        timer_second = findComponent(JComboBox.class, "timer_second");
        timer_weekday = findComponent(JList.class, "timer_weekday");
        timer_year = findComponent(JComboBox.class, "timer_year");
        txtIP = findComponent(JTextField.class, "txtIP");
        txtPort = findComponent(JTextField.class, "txtPort");
        autoUpdate = findComponent(JCheckBox.class, "autoUpdate");


        relaisButtons = new JButton[7];
        relaisButtons[0] = btnRelais1;
        relaisButtons[1] = btnRelais2;
        relaisButtons[2] = btnRelais3;
        relaisButtons[3] = btnRelais4;
        relaisButtons[4] = btnRelais5;
        relaisButtons[5] = btnRelais6;
        relaisButtons[6] = btnRelaisAux;

        timer_nr.addActionListener(this);
        btnDeleteTimer.addActionListener(this);
        btnReadRelais.addActionListener(this);
        btnReadTimer.addActionListener(this);
        btnRelais1.addActionListener(this);
        btnRelais2.addActionListener(this);
        btnRelais3.addActionListener(this);
        btnRelais4.addActionListener(this);
        btnRelais5.addActionListener(this);
        btnRelais6.addActionListener(this);
        btnRelaisAux.addActionListener(this);
        btnSetDateTime.addActionListener(this);
        btnUpdateSettings.addActionListener(this);
        btnWriteTimer.addActionListener(this);
        timer_getDateTime.addItemListener(this);
        autoUpdate.addItemListener(this);

        autoUpdateTimer = new Timer(5000, this);
        XPort = XPortControllerAsync.getInstance();
        initIP();
        initXPort();
    }

    @Override
    public void exceptionOccoured(TCPCommunicationException e) {
        appendMessage("TCP error during communication with XPort: " + e.getLocalizedMessage());
        EventQueue.invokeLater(new Runnable() {

            public void run() {
                autoUpdate.setSelected(false);
            }
        });
    }

    @Override
    public void exceptionOccoured(DataTransmissionException e) {
        appendMessage("Datatransmission error while reading Event: " + e.getLocalizedMessage());
    }

    @Override
    public void updateRelaisStatus(final boolean[] status) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                for (int i = 0; i < status.length; i++) {
                    if (status[i]) {
                        relaisButtons[i].setForeground(MainApplet.RELAIS_ON_COLOR);
                    } else {
                        relaisButtons[i].setForeground(MainApplet.RELAIS_OFF_COLOR);
                    }
                    relaisButtons[i].setEnabled(true);
                }

            }
        });
    }

    @Override
    public void updateTimer(final TimerEvent timer) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                updateTimerListModel(timer);
            }
        });
    }

    @Override
    public void updateTimerCount(int count) {
        //TODO implement a better solution
        XPort.readTimersAsync(this);
        appendMessage("Successfully performed Save/Delete task.");
    }

    @Override
    public void updateTimers(final TimerEvent[] timers) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                int tmp = timer_nr.getSelectedIndex();
                int index = tmp < 0 ? 0 : tmp;
                updateTimerListModel(timers);
                tmp = timer_nr.getItemCount();
                index = index < tmp ? index : tmp - 1;
                timer_nr.setSelectedIndex(index);
            }
        });
    }

//--------------------------- END of XPortCallbackIF implementation ------------------------------	

    // TODO Event Handler mittels Reflection anbinden
    @Override
    public void itemStateChanged(ItemEvent e) {
        Object source = e.getSource();
        if (source instanceof JCheckBox) {
            checkBoxItemStateChanged(e);
        } else {
            appendMessage("Unhandled ItemStateChangedEvent of Component: " + ((Component) source).getName());
        }
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        Object source = e.getSource();
        if (source instanceof AbstractButton) {
            buttonActionPerformed(e);
        } else if (source instanceof JComboBox) {
            comboBoxActionPerformed(e);
        } else if (source instanceof Timer) {
            XPort.readRelaisAsync(this);
        } else {
            appendMessage("Unhandled ActionEvent of Component: " + ((Component) source).getName());
        }

    }

    /**
     * Handles ActionEvents from JButtons
     * @param e
     * 			ActionEvent that should be handled
     */
    private void buttonActionPerformed(ActionEvent e) {
        AbstractButton button = (AbstractButton) e.getSource();

        // Handle relaisButtons
        for (int i = 0; i < relaisButtons.length; i++) {
            if (relaisButtons[i] == button) {
                XPort.toggleRelaisAsync(i, this);
                return;
            }
        }

        if (button == btnReadRelais) {
            XPort.readRelaisAsync(this);
        } else if (button == btnUpdateSettings) {
            initXPort();
        } else if (button == btnReadTimer) {
            XPort.readTimersAsync(this);
        } else if (button == btnWriteTimer) {
            TimerEvent timer = createTimerEvent();
            XPort.writeTimerAsync(timer.getTimerNr(), timer, this);
        } else if (button == btnDeleteTimer) {
            XPort.deleteTimerAsync(timer_nr.getSelectedIndex(), this);
        } else if (button == btnSetDateTime) {
            XPort.setDateTimeAsync(Calendar.getInstance(), this);
            appendMessage("Setting Date/Time to: " + Calendar.getInstance().getTime());
        } else {
            appendMessage("Unhandled ActionEvent of Button: " + button.getName());
        }

    }

    /**
     * Handles ActionEvents from JComboBoxes
     * @param e
     * 			ActionEvent that should be handled
     */
    private void comboBoxActionPerformed(ActionEvent e) {
        JComboBox combobox = (JComboBox) e.getSource();

        if (combobox == timer_nr) {
            Object item = combobox.getSelectedItem();
            if (item != null) {
                TimerEvent timer = (TimerEvent) item;
                updateTimerSelection(timer);

                if (timer.getTimerNr() == TimerEvent.DONTCARE) {
                    btnDeleteTimer.setEnabled(false);
                } else {
                    btnDeleteTimer.setEnabled(true);
                }
            }
        } else {
            appendMessage("Unhandled ActionEvent of JComboBox: " + combobox.getName());
        }
    }

    /**
     * Handles ActionEvents from JCheckBoxes
     * @param e
     * 			ActionEvent that should be handled
     */
    private void checkBoxItemStateChanged(ItemEvent e) {
        JCheckBox checkBox = (JCheckBox) e.getSource();

        if (checkBox == timer_getDateTime) {
            //deactivate Relais-lists if getDateTime event is selected
            if (checkBox.isSelected()) {
                timer_relais.clearSelection();
                timer_poweronoff.clearSelection();
                timer_relais.setEnabled(false);
                timer_poweronoff.setEnabled(false);
            } else {
                timer_relais.setEnabled(true);
                timer_poweronoff.setEnabled(true);
            }
        } else if (checkBox == autoUpdate) {
            if (checkBox.isSelected()) {
                autoUpdateTimer.start();
                btnReadRelais.setEnabled(false);
            } else {
                autoUpdateTimer.stop();
                btnReadRelais.setEnabled(true);
            }
        } else {
            appendMessage("Unhandled ItemStateChangedEvent of CheckBox: " + checkBox.getName());
        }

    }

//--------------------------- END of Event Handler implementation ------------------------------	
    /**
     * finds a named subcomponent within the controlled applet.
     * @param name
     * 			name of the component
     * @return
     * 			component
     * @throws NullPointerException
     * 			if no component found
     */
    private <T> T findComponent(Class<T> type, String name) throws NullPointerException {
        T comp = SwingTools.findComponent(type, name, applet, 10);
        if (comp == null) {
            appendMessage("Component '" + name + "' not found");
        }
        return comp;
    }

    private void appendMessage(final String msg) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                applet.appendMessage(msg);
            }
        });

    }

    /**
     * initializes the XPort. tries to set IP and Port that were entered into the textfields.
     */
    private void initXPort() {
        try {
            final int port = Integer.valueOf(txtPort.getText());
            new Thread() {

                @Override
                public void run() {
                    XPort.setIpAddress(txtIP.getText());
                    XPort.setPort(port);
                }
            }.start();
        } catch (NumberFormatException e) {
            appendMessage("Error setting port: " + txtPort.getText() + " is not a valid Portnumber.");
            txtPort.setText("");
            txtPort.requestFocusInWindow();
        }
    }

    private void initIP() {
        try {
            txtIP.setText(applet.getCodeBase().toString().substring(7, applet.getCodeBase().toString().length() - 1));
        } catch (Exception Ex) {
            appendMessage("Automatic determination of target IP failed");
        }
    }

    /**
     * updates the TimerlistModel when new values were read from XPort.
     * @param timers
     * 			timerarray that should be written into the model.
     */
    private void updateTimerListModel(TimerEvent[] timers) {
        DefaultComboBoxModel model = (DefaultComboBoxModel) timer_nr.getModel();
        model.removeAllElements();
        for (TimerEvent timer : timers) {
            model.addElement(timer);
        }
        model.addElement(new TimerEvent());
        btnWriteTimer.setEnabled(true);
    }

    /**
     * updates the TimerlistModel when new values were read from XPort.
     * @param timers
     * 			timerarray that should be written into the model.
     */
    private void updateTimerListModel(TimerEvent timer) {
        DefaultComboBoxModel model = (DefaultComboBoxModel) timer_nr.getModel();
        timer_nr.setSelectedIndex(timer.getTimerNr());
        model.setSelectedItem(timer);
        btnWriteTimer.setEnabled(true);
    }

    /**
     * updates the detailFields with the values retrieved from the selectedTimer.
     * @param selectedTimer
     * 				the selected TimerEvent object.
     */
    private void updateTimerSelection(TimerEvent selectedTimer) {
        int value;

        value = selectedTimer.getDay();
        timer_day.setSelectedIndex(value == TimerEvent.DONTCARE ? 0 : value);

        value = selectedTimer.getMonth();
        timer_month.setSelectedIndex(value == TimerEvent.DONTCARE ? 0 : value);

        value = selectedTimer.getYear();
        timer_year.setSelectedIndex(value == TimerEvent.DONTCARE ? 0 : value - 2007);

        value = selectedTimer.getHour();
        timer_hour.setSelectedIndex(value == TimerEvent.DONTCARE ? 0 : value + 1);

        value = selectedTimer.getMinute();
        timer_minute.setSelectedIndex(value == TimerEvent.DONTCARE ? 0 : value + 1);

        value = selectedTimer.getSecond();
        timer_second.setSelectedIndex(value == TimerEvent.DONTCARE ? 0 : value / 5);

        timer_getDateTime.setSelected(selectedTimer.getSpecial());

        timer_poweronoff.setSelectedIndices(selectedTimer.getDataArray());
        timer_relais.setSelectedIndices(selectedTimer.getChannels());
        timer_weekday.setSelectedIndices(selectedTimer.getWeekdays());

        timer_getDateTime.setSelected(selectedTimer.getSpecial());
    }

    /**
     * creates a TimerEvent object out of the selected data in the form.
     * @return
     * 		created TimerEvent object
     */
    private TimerEvent createTimerEvent() {
        TimerEvent event = new TimerEvent();
        int value;

        event.setSpecial(timer_getDateTime.isSelected());

        value = timer_day.getSelectedIndex();
        event.setDay(value == 0 ? TimerEvent.DONTCARE : value);

        value = timer_month.getSelectedIndex();
        event.setMonth(value == 0 ? TimerEvent.DONTCARE : value);

        value = timer_year.getSelectedIndex();
        event.setYear(value == 0 ? TimerEvent.DONTCARE : value + 2007);

        value = timer_hour.getSelectedIndex();
        event.setHour(value == 0 ? TimerEvent.DONTCARE : value - 1);

        value = timer_minute.getSelectedIndex();
        event.setMinute(value == 0 ? TimerEvent.DONTCARE : value - 1);

        value = timer_second.getSelectedIndex();
        event.setSecond(value * 5);

        event.setData(timer_poweronoff.getSelectedIndices());
        event.setChannel(timer_relais.getSelectedIndices());
        event.setWeekday(timer_weekday.getSelectedIndices());

        event.setTimerNr(timer_nr.getSelectedIndex());

        return event;
    }
}
